package com.gzsxy.esjy.sign.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

import com.gzsxy.esjy.sign.InnerSignCode;
import com.gzsxy.esjy.sign.SignConstant;
import com.gzsxy.esjy.sign.cache.SignCacheManager;
import com.gzsxy.esjy.sign.dto.AndroidSignProtocolDto;
import com.gzsxy.esjy.sign.dto.InnerSignDto;
import com.gzsxy.esjy.sign.dto.ScreenSignParamDto;
import com.gzsxy.esjy.sign.dto.ScreenSignProtocolDtoDto;
import com.gzsxy.esjy.sign.enums.SignTypeEnum;
import com.gzsxy.esjy.sign.exception.SignException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.Base64Utils;

import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.UUID;

/**
 * @author zhangxiaolong
 * @since 2022/7/15
 */
@Slf4j
public abstract class AbstractSignHandler {

    protected SignCacheManager signCacheManager;

    protected Integer serverPort;

    protected String localIp;

    protected String signOkPath = "sign/signOk";

    /**
     * 获取临时pdf存放地址
     *
     * @return 文件路径
     */
    public String getPdfTempPath() {
        String path = "./signTemp";
        File file = new File(path);
        if (!file.exists()) {
            file.mkdirs();
        }

        file.deleteOnExit();
        return path;
    }

    /**
     * 获取签名请求超时时间，-1,不做超时
     * 秒
     *
     * @return 超时时间
     */
    public long getSignTimeOut() {
        return 10 * 60 * 1000;
    }

    public String getUploadPdfUrl(String signId) {
        if (StringUtils.isBlank(localIp)){
            log.info("local Ip 为空，默认使用127.0.0.1");
            return String.format("http://127.0.0.1:%d/%s", serverPort, signOkPath);
        }

        return String.format("http://%s:%d/sign/uploadPdf/%s", localIp, serverPort, signId);
    }

    public String getSignOkUrl() {
        if (StringUtils.isBlank(localIp)){
            log.info("local Ip 为空，默认使用127.0.0.1");
            return String.format("http://127.0.0.1:%d/%s", serverPort, signOkPath);
        }

        return String.format("http://%s:%d/%s", localIp, serverPort, signOkPath);
    }

    /**
     * 保存签名文件
     *
     * @param uuid    业务标识
     * @param request 请求实体
     */
    public void uploadPdf(String uuid, HttpServletRequest request) {
        log.info("默认处理-接收到文件UUID:{}", uuid);

        String fileUuid = String.format("%s.pdf", UUID.randomUUID().toString());
        String savePath = Paths.get(getPdfTempPath(), uuid, fileUuid).toString();

        FileOutputStream fos = null;
        InputStream inputStream = null;
        try {
            File tempFile = new File(savePath);
            if (!tempFile.getParentFile().exists()){
                tempFile.getParentFile().mkdirs();
            }

            inputStream = request.getInputStream();
            fos = new FileOutputStream(tempFile);
            byte[] buffer = new byte[8192];
            for (int readSize; (readSize = inputStream.read(buffer)) != -1; ) {
                fos.write(buffer, 0, readSize);
                fos.flush();
            }
        } catch (Exception e) {
            log.error("写入文件失败：", e);
            throw new SignException("保存文件失败");
        } finally {
            try {
                if (null != fos) {
                    fos.close();
                }
            } catch (Exception ignored) {
            }

            try {
                if (null != fos) {
                    inputStream.close();
                }
            } catch (Exception ignored) {
            }
        }

        //添加缓存
        signCacheManager.getSignPdfCache().put(uuid, savePath);
    }

    /**
     * 获取安卓板签名文件base64
     *
     * @param signId 业务标识
     * @return 签名后的base64
     */
    public String getSignCachePdfBase64(String signId) {
        String pdfPath = signCacheManager.getSignPdfCache().get(signId);
        if (StringUtils.isBlank(pdfPath)) {
            return null;
        }

        File file = Paths.get(pdfPath).toFile();
        if (!file.exists()) {
            return null;
        }

        try {
            byte[] b = Files.readAllBytes(Paths.get(pdfPath));
            return Base64.getEncoder().encodeToString(b);
        } catch (Exception e) {
            log.error("读取文件失败：", e);
        }

        return null;
    }

    /**
     * 签名确认
     *
     * @param signDto 签名实体
     */
    public abstract void signAck(InnerSignDto signDto);

    /**
     * 签名成功
     *
     * @param signDto 签名实体
     */
    public abstract void signSuccess(InnerSignDto signDto);

    /**
     * 取消签名
     *
     * @param isDeviceCancel 是否设备取消
     * @param signDto        签名实体
     */
    public abstract void cancelSign(InnerSignDto signDto, boolean isDeviceCancel);

    /**
     * 签名异常，未处理的指令请求
     *
     * @param signDto 签名实体
     */
    public void signException(InnerSignDto signDto) {
        log.warn("签名异常：{}", JSON.toJSONString(signDto));
    }

    /**
     * 设备状态
     *
     * @param signDto 签名实体
     */
    public void deviceStatus(InnerSignDto signDto) {
        log.info("默认处理:设备状态,{}", JSON.toJSONString(signDto));
    }

    /**
     * 签名板请求超时
     *
     * @param signDto 签名实体
     */
    public void signRequestTimeOut(InnerSignDto signDto) {
        log.info("默认处理:收到超时回复,{}", JSON.toJSONString(signDto));
    }

    /**
     * 安卓签名处理
     *
     * @param signMsg 签名实体
     * @param taskId  业务标识，用户查询签名文件
     */
    public void androidSignOk(AndroidSignProtocolDto signMsg, String taskId) {
        //获取消息内容
        String msgPMsg = signMsg.getP_msg();
        JSONObject jsonObject = JSON.parseObject(msgPMsg);
        //获取指令cmd
        String cmd = jsonObject.getString("cmd");

        InnerSignDto innerSignDto = new InnerSignDto();
        innerSignDto.setCode(0);
        innerSignDto.setSignDevType(SignTypeEnum.ANDROID.getName());
        innerSignDto.setRequestId(taskId);
        innerSignDto.setCommand(cmd);
        innerSignDto.setTaskId(taskId);
        try {
            switch (cmd) {
                case SignConstant.P_SIGNBUSY: {
                    innerSignDto.setMsg("设备忙碌");
                    log.warn("设备忙碌");
                    innerSignDto.setCode(InnerSignCode.BUSY_SIGN_CODE);
                    deviceStatus(innerSignDto);
                    break;
                }
                case SignConstant.P_SIGNCANCEL: {
                    log.info("取消签名");
                    innerSignDto.setMsg("取消签名");
                    innerSignDto.setCode(InnerSignCode.DEVICE_CANCEL_SIGN_CODE);
                    cancelSign(innerSignDto, true);
                    break;
                }
                case SignConstant.P_STARTSIGN:{
                    log.info("开始签名：{}", msgPMsg);
                    innerSignDto.setCode(InnerSignCode.START_SIGN);
                    JSONObject parameters = jsonObject.getJSONObject("parameters");
                    Integer currentpage = parameters.getInteger("p_currentpage");
                    Integer totalpage = parameters.getInteger("p_totalpage");
                    innerSignDto.setMsg(String.format("共%d页,正在第%d页签名", totalpage, currentpage));
                    //deviceStatus(innerSignDto);//前端未处理开始签名，暂时不发消息给前端
                    log.info("开始签名响应结果：{}",innerSignDto);
                    return;
                }
                case SignConstant.P_SIGNOK: {
                    log.info("签名成功");

                    String filePath = signCacheManager.getSignPdfCache().get(taskId);
                    File file = new File(filePath);
                    if (StringUtils.isNotBlank(filePath) && file.exists()) {
                        byte[] bytes = readBytes(new File(filePath));
                        if (null == bytes) {
                            innerSignDto.setMsg("读取本地缓存文件失败");
                            signException(innerSignDto);
                        } else {
                            String base64 = Base64Utils.encodeToString(bytes);
                            innerSignDto.setSignPdfBase64(base64);
                            innerSignDto.setMsg("签名成功");
                            signSuccess(innerSignDto);
                        }
                    } else {
                        innerSignDto.setMsg("读取本地缓存文件失败");
                        signException(innerSignDto);
                    }
                    break;
                }
                case SignConstant.P_SIGN: {
                    JSONObject parameters = jsonObject.getJSONObject("parameters");
                    if(parameters == null){
                        log.info("parameters参数为空：{}", msgPMsg);
                    }else{
                        Integer p_code = parameters.getInteger("p_code");
                        if(p_code != 0){
                            innerSignDto.setMsg("设备忙碌或正在进行其它操作");
                            log.info("设备忙碌或正在进行其它操作");
                            innerSignDto.setCode(InnerSignCode.BUSY_SIGN_CODE);
                            deviceStatus(innerSignDto);
                        }else{
                            return;
                        }
                    }
                    break;
                }

                default:
                    log.info("收到回复,未实现处理：{}", msgPMsg);
                    return;
            }
        } catch (Exception e) {
            log.error("业务处理异常：{}", e.getMessage());
        }

        //进行缓存清理
        signCacheManager.clearSignByRequestId(taskId, true, true);
    }

    /**
     * 投屏板签名处理
     *
     * @param screenSignProtocolDto 投屏版实体
     */
    public void signOk(ScreenSignProtocolDtoDto screenSignProtocolDto) {
        JSONObject penSignerProtocolObj = screenSignProtocolDto.getResult();
        ScreenSignParamDto psMsg = JSONObject.parseObject(JSONObject.toJSONString(penSignerProtocolObj), ScreenSignParamDto.class);
        int code = psMsg.getCode();
        String command = screenSignProtocolDto.getCommand();

        InnerSignDto innerSignDto = new InnerSignDto();
        innerSignDto.setCode(0);
        innerSignDto.setSignDevType(SignTypeEnum.SCREEN.getName());
        innerSignDto.setMsg(psMsg.getMsg());
        innerSignDto.setRequestId(psMsg.getRequestID());
        innerSignDto.setCommand(command);
        innerSignDto.setTaskId(psMsg.getTaskID());

        //签名确认信息 签字板接收到信息后回复
        if (null != psMsg.getAck() && psMsg.getAck() == 0) {
            signAck(innerSignDto);
            return;
        }

        if (!signCacheManager.getSignTaskCache().containsKey(psMsg.getRequestID())) {
            log.warn("任务不存在, 不处理当前请求：{}", JSON.toJSONString(screenSignProtocolDto));
            return;
        }

        //超时处理
        String taskId = psMsg.getTaskID();
        long signTimeOut = getSignTimeOut();
        if (StringUtils.isNotBlank(taskId) && -1 != signTimeOut) {
            long millis = System.currentTimeMillis();
            long time = 0;
            try {
                time = Long.parseLong(taskId);
            } catch (Exception ignored) {
            }

            if (0 != time && (millis - time) > signTimeOut) {
                log.warn("收到超时请求数据:{}", JSON.toJSONString(screenSignProtocolDto));
                signRequestTimeOut(innerSignDto);
                signCacheManager.clearSignByRequestId(psMsg.getRequestID(), true, true);
                return;
            }
        }

        try {
            switch (code) {
                case 0: {
                    if (SignConstant.SIGN_CMD.equalsIgnoreCase(command)) {
                        String signedPdf = psMsg.getDevMsg().getSignedPdf();
                        innerSignDto.setSignPdfBase64(signedPdf);
                        signSuccess(innerSignDto);
                    } else {
                        log.info("收到回复,未实现处理：{}", JSON.toJSONString(screenSignProtocolDto));
                        return;
                    }
                    break;
                }
                case SignConstant.DEVICE_CANCEL_CODE:
                    innerSignDto.setCode(InnerSignCode.DEVICE_CANCEL_SIGN_CODE);
                    cancelSign(innerSignDto, true);
                    break;
                case SignConstant.FILE_NOT_FOUND_CODE:
                    innerSignDto.setCode(InnerSignCode.FILE_DOWNLOAD_ERROR_CODE);
                    cancelSign(innerSignDto, true);
                    break;
                case SignConstant.DEVICE_IDLE:
                case SignConstant.DEVICE_BUSY_CODE:
                    innerSignDto.setCode(InnerSignCode.BUSY_SIGN_CODE);
                    deviceStatus(innerSignDto);
                    break;
                case SignConstant.REMOTE_CANCEL_CODE:
                    innerSignDto.setCode(InnerSignCode.CANCEL_SIGN_CODE);
                    cancelSign(innerSignDto, false);
                    signCacheManager.clearSignByRequestId(psMsg.getRequestID(), false, true);
                    return;
                case SignConstant.DEVICE_DISCONNECT_CODE:
                    innerSignDto.setCode(InnerSignCode.DISCONNECT_ERROR_CODE);
                    cancelSign(innerSignDto, true);
                    break;
                default:
                    log.info("收到回复,未实现处理：{}", JSON.toJSONString(screenSignProtocolDto));
                    innerSignDto.setCode(code);
                    signException(innerSignDto);
                    return;
            }
        } catch (Exception e) {
            log.warn("业务处理异常：{}", e.getMessage());
        }

        //进行缓存清理
        signCacheManager.clearSignByRequestId(psMsg.getRequestID(), true, true);
    }

    /**
     * 读取文件
     *
     * @param file 文件
     */
    public byte[] readBytes(File file) {
        long len = file.length();
        if (len >= Integer.MAX_VALUE) {
            return null;
        }

        byte[] bytes = new byte[(int) len];
        int readLength;
        try (FileInputStream in = new FileInputStream(file)) {
            readLength = in.read(bytes);
            if (readLength < len) {
                return null;
            }
        } catch (Exception e) {
            return null;
        }

        return bytes;
    }
}
